package cn.wlinker.driver.bacnet.utils;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.log.StaticLog;
import cn.wlinker.driver.bacnet.domain.BacnetLocalDeviceBean;
import cn.wlinker.driver.common.constant.Constant;
import cn.wlinker.driver.common.exception.DriverInitException;
import com.serotonin.bacnet4j.LocalDevice;
import com.serotonin.bacnet4j.npdu.ip.IpNetwork;
import com.serotonin.bacnet4j.npdu.ip.IpNetworkBuilder;
import com.serotonin.bacnet4j.transport.DefaultTransport;

import java.util.Optional;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

/**
 * 虚拟bacnet设备
 *
 * @author gxsjx
 * @version 1.0
 * @date 2022/9/18
 * Copyright © wlinker.cn
 */
public class BacnetLocalDeviceHelper {


    public static final int LOCAL_DEVICE_ID = 99999;
    public static final int BASE_MIN_ID = 250000;
    public static final Random RANDOM = new Random();

    public static synchronized LocalDevice getLocalDevice(BacnetLocalDeviceBean bacnetLocalDeviceBean) throws Exception {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bacnetLocalDeviceBean);
        if (ObjectUtil.isNotNull(localDevice) && localDevice.isInitialized()) {
            //startRemoteDeviceDiscovery(localDevice);
            return localDevice;
        } else {
            return initLocalDevice(bacnetLocalDeviceBean);
        }
    }

    private static synchronized LocalDevice initLocalDevice(BacnetLocalDeviceBean bacnetLocalDeviceBean) throws Exception {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bacnetLocalDeviceBean);
        if (ObjectUtil.isNotNull(localDevice) && !localDevice.isInitialized()) {
            localDevice.terminate();
        }
        try {
            String localBindAddress = bacnetLocalDeviceBean.getLocalBindAddress();
            IpNetworkBuilder ipNetworkBuilder = new IpNetworkBuilder()
                    .withLocalBindAddress(localBindAddress)
                    .withSubnet("255.255.255.0", 24)
                    .withPort(47808)
                    .withReuseAddress(true);
            String broadcastAddress = bacnetLocalDeviceBean.getBroadcastAddress();
            Integer networkPrefixLength = bacnetLocalDeviceBean.getNetworkPrefixLength();
            if (CharSequenceUtil.isNotBlank(broadcastAddress) && ObjectUtil.isNotEmpty(networkPrefixLength)) {
                ipNetworkBuilder.withBroadcast(broadcastAddress, networkPrefixLength);
            }
            IpNetwork network = ipNetworkBuilder.build();
            int localDeviceId = RANDOM.nextInt(LOCAL_DEVICE_ID) + BASE_MIN_ID;
            StaticLog.info("localDeviceId:{}", localDeviceId);
            DefaultTransport defaultTransport = new DefaultTransport(network);
            localDevice = new LocalDevice(localDeviceId, defaultTransport);
            MyListener myListener = new MyListener(localDevice);
            localDevice.getEventHandler().addListener(myListener);
            localDevice.initialize();
            // Wait a bit for initialize.
            for (int i = 1; i <= Constant.RETRIES; i++) {
                Boolean put = LocalDeviceCacheUtils.put(bacnetLocalDeviceBean, localDevice);
                if (Boolean.TRUE.equals(put)) {
                    break;
                }
                Thread.sleep(Constant.TIMEOUT_MS);
            }
            startRemoteDeviceDiscovery(localDevice);
        } catch (InterruptedException e) {
            if (ObjectUtil.isNotNull(localDevice)) {
                localDevice.terminate();
            }
            throw new DriverInitException(e);
        }
        return localDevice;
    }

    private static void startRemoteDeviceDiscovery(LocalDevice localDevice) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        Thread thread = Thread.currentThread();
        localDevice.startRemoteDeviceDiscovery(t -> {
            int andIncrement = atomicInteger.incrementAndGet();
            if (andIncrement <= 1) {
                LockSupport.unpark(thread);
            }
        });
        LockSupport.parkNanos(localDevice, Constant.FIRST_REMOTE_DEVICE_FIND_TIMEOUT_NS);
        while (true) {
            int count = atomicInteger.get();
            Thread.sleep(Constant.REMOTE_DEVICE_FIND_TIMEOUT_MS);
            if (count == atomicInteger.get()) {
                break;
            }
        }
    }

    public static Boolean connect(BacnetLocalDeviceBean bacnetLocalDeviceBean) throws Exception {
        LocalDevice localDevice = getLocalDevice(bacnetLocalDeviceBean);
        return localDevice.isInitialized();
    }

    public static Boolean isOpen(BacnetLocalDeviceBean bacnetLocalDeviceBean) {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bacnetLocalDeviceBean);
        return Optional.ofNullable(localDevice).isPresent() && localDevice.isInitialized();
    }

    public static Boolean close(BacnetLocalDeviceBean bacnetLocalDeviceBean) {
        LocalDevice localDevice = LocalDeviceCacheUtils.get(bacnetLocalDeviceBean);
        if (localDevice != null) {
            localDevice.terminate();
        }
        return true;
    }


}


